import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import Interfaces.IAgentDownHandler;
import Interfaces.IAgentUpHandler;
import Interfaces.IDataHandler;
import Messages.DiscoveryMessage;
import Messages.MonitorDataTypeEnum;

public class MonitorModel
{
	private Configuration m_configuration;
	private ManagmentChannelListener m_managmentChannelListener;
	private DataChannelListener m_dataChannelListener;
	private CommandChannel m_commandChannel;
	private List<INewAgentHandler> m_newAgentHandlers;
	private List<IAgentTimeoutHandler> m_agentTimeoutHandlers;
	private String m_managmentChannelHost;
	private int m_managmentChannelPort;
	private int m_dataChannelPort;
	private List<IMonitorDataHandler> m_monitorDataHandlers;
	
	public MonitorModel()
	{
		try 
		{
			m_newAgentHandlers = new ArrayList<INewAgentHandler>();
			m_agentTimeoutHandlers = new ArrayList<IAgentTimeoutHandler>();
			m_monitorDataHandlers = new ArrayList<IMonitorDataHandler>();
			m_configuration = new Configuration("src/config.properties");
			
			// Create management listener (listens to agents discovery messages)
			m_managmentChannelHost = m_configuration.getParameter("ManagmentChannelHost");
			m_managmentChannelPort = Integer.parseInt(m_configuration.getParameter("ManagmentChannelPort"));
			m_managmentChannelListener = new ManagmentChannelListener(m_managmentChannelHost, m_managmentChannelPort);
			
			// Create agent up handler and add it 
			m_managmentChannelListener.AddAgentUpHandler(new IAgentUpHandler() {
				
				@Override
				public void HandleAgentUp(DiscoveryMessage discMessage) {
					Agent agent = CreateAgent(discMessage);
					for(INewAgentHandler handler:m_newAgentHandlers)
					{
						handler.HandleNewAgent(agent);
					}	
				}});
			// Create agent down handler and add it
			m_managmentChannelListener.AddAgentDownHandler(new IAgentDownHandler() {
				
				@Override
				public void HandleAgentDown(String host) 
				{	
					for(IAgentTimeoutHandler handler:m_agentTimeoutHandlers)
					{
						handler.HandleAgentTimeout(host);
					}	
				}
			});
			
			// Create data channel listener
			m_dataChannelPort = Integer.parseInt(m_configuration.getParameter("DataChannelPort"));
			m_dataChannelListener = new DataChannelListener(m_dataChannelPort, new IDataHandler() 
			{	
				@Override
				public void HandleData(String agentIP,
									   Messages.MonitorDataTypeEnum monitorDataType,
									   Object[] parameters, 
									   Date timeStamp, 
									   String data) 
				{	
					// TODO: add host to data channel message
					HandleMonitordata(agentIP,"bla",monitorDataType,parameters, timeStamp,data);
				}
			});
			
			// Create command channel
			m_commandChannel = new CommandChannel();
		} 
		catch (UnknownHostException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		catch (IOException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void HandleMonitordata(String agentIP,
								   String agentHost,
								   MonitorDataTypeEnum monitorDataType, 
								   Object[] parameters,
								   Date timeStamp, 
								   String data) 
	{
		MonitorData monitorData = new MonitorData(agentIP, agentHost, ToView(monitorDataType));
		monitorData.setTimeStamp(timeStamp);
		monitorData.setValue(data);
		if(parameters != null && parameters.length>0 && parameters[0]!= null)
		{
			monitorData.setParameter(parameters[0].toString());
		}
		for(IMonitorDataHandler handler:m_monitorDataHandlers)
		{
			handler.HandleMonitorData(monitorData);
		}
	}

	// TODO: add other monitor type values
	private MonitorTypeEnum ToView(MonitorDataTypeEnum monitorDataType)
	{
		switch(monitorDataType)
		{
			case Cpu:
				return MonitorTypeEnum.CpuUsage;
			case Disk:
				return MonitorTypeEnum.DiskUsage;
			case Ping:
				return MonitorTypeEnum.Ping;
			case Memory:
				return MonitorTypeEnum.MemoryUsage;
			default:
				throw new InvalidParameterException();
		}
	}
	
	private MonitorDataTypeEnum ToProtocol(MonitorTypeEnum monitorType)
	{
		switch (monitorType) {
		case CpuUsage:	
			return MonitorDataTypeEnum.Cpu;
		case DiskUsage:
			return MonitorDataTypeEnum.Disk;
		case Ping:
			return MonitorDataTypeEnum.Ping;
		case MemoryUsage:
			return MonitorDataTypeEnum.Memory;
		default:
			throw new InvalidParameterException();
		}
	}
	
	private Agent CreateAgent(DiscoveryMessage agentDiscMessage) 
	{
		// TODO: add monitor capabilities to discovery message
		MonitorTypeEnum[] monitoringCapabilities = new MonitorTypeEnum[]{
				MonitorTypeEnum.CpuUsage,
				MonitorTypeEnum.DiskUsage,
				MonitorTypeEnum.Ping,
				MonitorTypeEnum.MemoryUsage};
		Agent agent = new Agent(agentDiscMessage.AgentIP, 
								agentDiscMessage.AgentHost,
								agentDiscMessage.AgentListeningPort,
								monitoringCapabilities);
		return agent;
	}
	
	public void Start()
	{
		m_managmentChannelListener.startListening();
		m_dataChannelListener.startListening();
	}
	
	public void Stop()
	{
		m_managmentChannelListener.stopListening();
		m_dataChannelListener.stopListening();
	}
	
	public void RegisterMonitorData(MonitorData monitorData)
	{	
		// TODO: maybe change parameters in protocol to String?
		if (!m_commandChannel.Register(monitorData.getIP(),
									   monitorData.getPort(),
									   m_dataChannelPort, 
									   ToProtocol(monitorData.getMonitorDataType()), 
			       					   new Object[]{monitorData.getParameter()}))
		{
			System.out.print("register failed");
		}
		else
		{
			System.out.print("register pass");
		}
	}
	
	public void UnregisterMonitorData(MonitorData monitorData)
	{
		if(!m_commandChannel.Unregister(monitorData.getIP(), 
										monitorData.getPort(), 
										ToProtocol(monitorData.getMonitorDataType())))
		{
			System.out.print("unregister failed");
		}
		else
		{
			System.out.print("unregister pass");
		}
	}
	
	public void AddNewAgentHandler(INewAgentHandler handler)
	{
		m_newAgentHandlers.add(handler);
	}
	
	public void AddMonitorDataHandler(IMonitorDataHandler handler)
	{
		m_monitorDataHandlers.add(handler);
	}
	
	public void AddAgentTimeoutHandler(IAgentTimeoutHandler handler)
	{
		m_agentTimeoutHandlers.add(handler);
	}
}
